﻿using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Editing;

namespace Actor.Analyzer
{
    [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ActorAnalyzerCodeFixProvider)), Shared]
    public class ActorAnalyzerCodeFixProvider : CodeFixProvider
    {
        public sealed override ImmutableArray<string> FixableDiagnosticIds
        {
            get { return ImmutableArray.Create(ActorAnalyzerAnalyzer.MethodNeedFuncAttributeId, ActorAnalyzerAnalyzer.MethodNeedFuncAttributeId); }
        }

        public sealed override FixAllProvider GetFixAllProvider()
        {
            // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
            return WellKnownFixAllProviders.BatchFixer;
        }

        public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

            // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest
            var diagnostic = context.Diagnostics.First();
            var diagnosticSpan = diagnostic.Location.SourceSpan;

            // Find the type declaration identified by the diagnostic.
            var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<TypeDeclarationSyntax>().First();

            // Register a code action that will invoke the fix.
            context.RegisterCodeFix(
                CodeAction.Create(
                    title: CodeFixResources.CodeFixTitle,
                    createChangedSolution: c => AddFuncAttributeAsync(context, context.Document, declaration, c),
                    equivalenceKey: nameof(CodeFixResources.CodeFixTitle)),
                diagnostic);
        }

        private async Task<Solution> AddFuncAttributeAsync(CodeFixContext context, Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken)
        {
            // Get the symbol representing the type to be renamed.
            var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
            var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken);

            var typeInfo = context.Diagnostics[0]?.Properties.GetValueOrDefault("type", null);
            var members = typeSymbol.GetMembers().OfType<IMethodSymbol>();
            IMethodSymbol methodSymbol = null;
            foreach (var member in members)
            {
                var memberString = member.ToString();
                if(memberString == typeInfo)
                {
                    methodSymbol = member;
                    break;
                }
            }

            if (methodSymbol == null)
            {
                return document.Project.Solution;
            }

            var solutionEditor = new SolutionEditor(document.Project.Solution);
            SyntaxNode node = await methodSymbol.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false);
            var documentEditor = await solutionEditor.GetDocumentEditorAsync(document.Project.Solution.GetDocumentId(node.SyntaxTree), cancellationToken).ConfigureAwait(false);
            var syntaxGenerator = SyntaxGenerator.GetGenerator(documentEditor.OriginalDocument);

            var returnString = methodSymbol.ReturnType.ToString();
            var parameter = methodSymbol.Parameters[0].ToString().Split('.').Last();
            if (returnString.Contains("System.Threading.Tasks.Task<"))
            {
                returnString = returnString.Replace("System.Threading.Tasks.Task<", string.Empty);
                returnString = returnString.Replace(">", string.Empty);
                returnString = returnString.Split('.').Last();
                var declaredString = $"Func(typeof(ActorParameter<{returnString}, {parameter}>))";
                documentEditor.AddAttribute(node, syntaxGenerator.Attribute(declaredString));
            }
            if (returnString == "System.Threading.Tasks.Task" || returnString == "void")
            {
                var declaredString = $"Func(typeof(ActorParameter<{parameter}>))";
                documentEditor.AddAttribute(node, syntaxGenerator.Attribute(declaredString));
            }

            return solutionEditor.GetChangedSolution();
        }
    }
}
